#coding=utf-8
import logging, threading, transaction, json
from datetime import datetime as dt
from sqlalchemy import Column, Integer, Unicode, DateTime, Boolean, Numeric
from ext.model import DBSession
from ext.view import get_user_id

log = logging.getLogger(__name__)

__ALL__ = ['StaticMixin', 'DynamicMixin', 'SequenceMixin']

ACTIVE_TRUE = 't'
ACTIVE_FALSE = 'f'

class AttributeDict(dict):
    def __getattr__(self, attr):
        return self[attr]
    def __setattr__(self, attr, value):
        self[attr] = value

class StaticMixin():

    id = Column(Integer, primary_key=True)

    @property
    def attribute_dict(self):
        return AttributeDict(self.__dict__)

    @classmethod
    def get(cls, id):
        return DBSession.query(cls).get(id)

    @classmethod
    def all(cls, order_func='id asc'):
        return DBSession.query(cls).order_by(order_func).all()

    @classmethod
    def find_by(cls, order_func='id asc', all=True, **kw):
        keytypeMap = {}
        for i in cls.__table__.columns:
            keytypeMap.update({i.key: type(i.type)})
        qyModel = DBSession.query(cls)
        for k,v in kw.iteritems():
            if k in keytypeMap.keys():
                if type(v)==list or type(v)==tuple:
                    qyModel = qyModel.filter(getattr(cls, k).in_(v))
                else:
                    if keytypeMap[k] == Boolean and v==False or v==True:
                        qyModel = qyModel.filter(getattr(cls, k)==v)
                    elif v or str(v):
                        if keytypeMap[k] == Integer:
                            qyModel = qyModel.filter(getattr(cls, k)==int(v))
                        elif keytypeMap[k] == DateTime:
                            qyModel = qyModel.filter(and_(getattr(cls, k)==int(v)))
                        else:
                            qyModel = qyModel.filter(getattr(cls, k).ilike('%%%s%%' % v.strip()))
        qyModel = qyModel.order_by(order_func)
        return qyModel.all() if all else qyModel

    '''
    @classmethod
    def find_by(cls, all=True, order_func='id asc', **kw):
        keytypeMap = {}
        for i in cls.__table__.columns:
            keytypeMap.update({i.key: type(i.type)})
        qyModel = DBSession.query(cls)
        for k,v in kw.iteritems():
            if k in keytypeMap.keys():
                    if keytypeMap[k] == Integer:
                        if v:
                            qyModel = qyModel.filter(getattr(cls, k)==int(v))
                    else:
                        qyModel = qyModel.filter(getattr(cls, k).ilike('%%%s%%' % v))
        qyModel = qyModel.order_by(order_func)
        return qyModel.all() if all else qyModel
    '''

    @classmethod
    def get_by(cls, **kw):
        return cls.find_by(all=False, **kw).first()

    @classmethod
    def find_by_ids(cls, ids, separator=',', order_func='id asc'):
        if type(ids) == str or type(ids) == unicode:
            results = []
            id_list = filter(lambda x: x, ids.split(separator))
            result_dict = {}
            for result in DBSession.query(cls).filter(cls.id.in_(id_list)).order_by(order_func).all():
                result_dict[result.id] = result
            for id in ids.split(separator):
                results.append(result_dict[int(id)] if id else None)
            return results
        elif type(ids) == list or type(ids) == tuple:
            return DBSession.query(cls).filter(cls.id.in_(ids)).order_by(order_func).all()

    def to_json(self, * args):
        json_dict = {}
        if not args:
            args = self.__dict__.keys()
        for i in args:
            v = self.__dict__[i]
            if isinstance(v, dt):
                json_dict[i] = v.strftime('%Y-%m-%d %H:%M')
            else:
                json_dict[i] = v
        return json_dict

class DynamicMixin(StaticMixin):

    create_time = Column(DateTime, default=dt.now)
    create_by_id = Column(Integer, default=get_user_id)
    update_time = Column(DateTime, default=dt.now, onupdate=dt.now)
    update_by_id = Column(Integer, default=get_user_id, onupdate=get_user_id)
    active = Column(Boolean, default=True)

    @property
    def create_by(self):
        return DBSession.query(User).get(self.create_by_id)

    @property
    def update_by(self):
        return DBSession.query(User).get(self.update_by_id)

    @classmethod
    def find_by(cls, order_func='create_time desc', all=True, active=True, **kw):
        qyModel = super(DynamicMixin, cls).find_by(order_func=order_func, all=False, **kw)
        qyModel = qyModel.filter(cls.active==active)
        return qyModel.all() if all else qyModel

    @classmethod
    def init_new(cls, *keys, **kw):
        if kw.get('id', None):
            del kw['id']
        return cls.init(*keys, **kw)

    @classmethod
    def init(cls, *keys, **kw):
        try:
            obj = cls(**cls._resetKw(*keys, **kw))
            return obj
        except Exception, e:
            log.exception(str(e))
            raise

    @classmethod
    def create(cls, *keys, **kw):
        try:
            obj = cls.init(*keys, **kw)
            DBSession.add(obj)
            DBSession.flush()
            return obj
        except Exception, e:
            transaction.doom()
            log.exception(str(e))
            raise

    def update(self, *keys, **kw):
        try:
            new_params = self.__class__._resetKw(*keys, **kw)
            old_params = self.__dict__
            for k,v in new_params.iteritems():
                if not old_params.get(k, None) == v:
                    setattr(self, k, v)
            DBSession.flush()
            return self
        except Exception, e:
            transaction.doom()
            log.exception(str(e))
            raise

    @classmethod
    def deactive(cls, id):
        obj = cls.get(id)
        obj.active = False
        return obj

    @classmethod
    def enactive(cls, id):
        obj = cls.get(id)
        obj.active = True
        return obj

    @classmethod
    def _resetKw(cls, *keys, **kw):
        keytypeMap = {}
        for i in cls.__table__.columns:
            keytypeMap.update({i.key: type(i.type)})
        params = {}
        for k,v in kw.iteritems():
            if not k.startswith('_') and k in keytypeMap.keys():
            #if k in keytypeMap.keys():
                if type(v) == list or type(v) == tuple:
                    params[k] = ','.join(map(str, v))
                elif type(v) == dict:
                    params[k] = json.dumps(v)
                elif k.endswith('_id') or k=='id':
                    params[k] = int(v) if v else None
                else:
                    if (keytypeMap[k]==Integer or keytypeMap[k]==Numeric) and not v:
                        params[k] = None
                    elif keytypeMap[k]==Boolean:
                        params[k] = True if v=='True' or v==True or v=='t' else False
                    elif keytypeMap[k]==DateTime and not v:
                        params[k] = None
                    else:
                        params[k] = v
        return params

    def compare(self, old_obj, *columns):
        diffs = []
        map = {}

        def _do(v):
            if v.info.has_key('label'):
                new_value = getattr(self, v.name)
                old_value = old_obj[v.name]
                if type(v.type) == Integer:
                    old_value = int(old_value) if old_value else old_value
                    new_value = int(new_value) if new_value else new_value
                if new_value != old_value:
                    if v.info.has_key('desc'):
                        if callable(v.info['desc']):
                            diffs.append((v.info['label'], old_value, new_value, v.info['desc'](old_value), v.info['desc'](new_value)))
                        else:
                            diffs.append((v.info['label'], old_value, new_value, v.info['desc'][str(old_value)], v.info['desc'][str(new_value)]))
                    else:
                        diffs.append((v.info['label'], old_value, new_value))

        for i in self.__class__.__table__.columns:
            map.update({i.key: i})
        if columns:
            for i in columns:
                _do(map.get(i))
        else:
            for v in map.values():
                _do(v)
        return diffs

    def disp_time(self, _time, format='%Y-%m-%d %H:%M:%S'):
        return _time.strftime(format)

class SequenceMixin(DynamicMixin):

    name = Column(Unicode(100))
    start = Column(Integer, default=0)

    @classmethod
    def new_sequence(cls, name):
        lock = threading.RLock()
        with lock:
            sequence = cls.get_by(name=name)
            if not sequence:
                sequence = cls.create(name=name)
                sequence.start = 0
            sequence.start += 1
            return sequence.start

